﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using MSNPSharp;

namespace MessengerBot
{
    /// <summary>
    /// This is the core bot implementation, the public interface for
    /// programs that uses the bot.
    /// </summary>
    public sealed class Bot : IDisposable
    {
        /// <summary>
        /// This field holds the mapping from command names to their respective command types.
        /// </summary>
        private readonly CommandMap _Commands;

        /// <summary>
        /// This is the backing field for the <see cref="Configuration"/> property.
        /// </summary>
        private readonly BotConfiguration _Configuration;

        /// <summary>
        /// This <see cref="AutoResetEvent"/> is used to ask the bot to exit gracefully.
        /// </summary>
        private readonly AutoResetEvent _ExitEvent;

        /// <summary>
        /// This <see cref="ManualResetEvent"/> is used by the to to signal that it has exited.
        /// </summary>
        private readonly ManualResetEvent _ExitedEvent;

        /// <summary>
        /// This is the <see cref="Messenger"/> instance this <see cref="Bot"/> manages.
        /// </summary>
        private readonly Messenger _Messenger;

        /// <summary>
        /// This field holds a collection of open <see cref="Session"/> objects.
        /// </summary>
        private readonly List<Session> _Sessions = new List<Session>();

        /// <summary>
        /// This field will be set to <c>true</c> when <see cref="Dispose"/> is called.
        /// </summary>
        private bool _IsDisposed;

        /// <summary>
        /// Initializes a new instance of the <see cref="Bot"/> class.
        /// </summary>
        /// <param name="configuration">
        /// The configuration for this <see cref="Bot"/>.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="configuration"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// <para><paramref name="configuration"/>.<see cref="BotConfiguration.Username">Username</see> is <c>null</c> or empty.</para>
        /// <para>- or -</para>
        /// <para><paramref name="configuration"/>.<see cref="BotConfiguration.Username">Password</see> is <c>null</c> or empty.</para>
        /// </exception>
        public Bot(BotConfiguration configuration)
        {
            if (configuration == null)
                throw new ArgumentNullException("configuration");
            configuration.Validate();

            if (configuration.Logger == null)
                configuration.Logger = new VoidLogger();

            configuration.Freeze();
            _Configuration = configuration;

            _Commands = new CommandMap(configuration.Systems.OfType<ICommandBasedSystem>());

            _ExitEvent = new AutoResetEvent(false);
            _ExitedEvent = new ManualResetEvent(false);

            _Messenger = new Messenger();
            Settings.TraceSwitch.Level = TraceLevel.Off;
            _Messenger.Credentials = new Credentials(Configuration.Username, Configuration.Password);
            _Messenger.ConversationCreated += ConversationCreated;
            _Messenger.Nameserver.AuthenticationError += Nameserver_AuthenticationError;
            _Messenger.Nameserver.CircleMemberOffline += Nameserver_CircleMemberOffline;
            _Messenger.Nameserver.CircleMemberOnline += Nameserver_CircleMemberOnline;
            _Messenger.Nameserver.CircleMemberStatusChanged += Nameserver_CircleMemberStatusChanged;
            _Messenger.Nameserver.CircleNudgeReceived += Nameserver_CircleNudgeReceived;
            _Messenger.Nameserver.CircleOffline += Nameserver_CircleOffline;
            _Messenger.Nameserver.CircleOnline += Nameserver_CircleOnline;
            _Messenger.Nameserver.CircleStatusChanged += Nameserver_CircleStatusChanged;
            _Messenger.Nameserver.CircleTextMessageReceived += Nameserver_CircleTextMessageReceived;
            _Messenger.Nameserver.ContactOffline += Nameserver_ContactOffline;
            _Messenger.Nameserver.ContactOnline += Nameserver_ContactOnline;
            _Messenger.Nameserver.ContactStatusChanged += Nameserver_ContactStatusChanged;
            _Messenger.Nameserver.CrossNetworkMessageReceived += Nameserver_CrossNetworkMessageReceived;
            _Messenger.Nameserver.ExceptionOccurred += Nameserver_ExceptionOccurred;
            _Messenger.Nameserver.JoinedCircleConversation += Nameserver_JoinedCircleConversation;
            _Messenger.Nameserver.LeftCircleConversation += Nameserver_LeftCircleConversation;
            _Messenger.Nameserver.MailboxChanged += Nameserver_MailboxChanged;
            _Messenger.Nameserver.MailboxStatusReceived += Nameserver_MailboxStatusReceived;
            _Messenger.Nameserver.MobileMessageReceived += Nameserver_MobileMessageReceived;
            _Messenger.Nameserver.NewMailReceived += Nameserver_NewMailReceived;
            _Messenger.Nameserver.OwnerVerified += Nameserver_OwnerVerified;
            _Messenger.Nameserver.PingAnswer += Nameserver_PingAnswer;
            _Messenger.Nameserver.SBCreated += Nameserver_SBCreated;
            _Messenger.Nameserver.ServerErrorReceived += Nameserver_ServerErrorReceived;
            _Messenger.Nameserver.SignedIn += Nameserver_SignedIn;
            _Messenger.Nameserver.SignedOff += Nameserver_SignedOff;
        }

        /// <summary>
        /// Gets the <see cref="BotConfiguration"/> that is used for this <see cref="Bot"/>.
        /// </summary>
        public BotConfiguration Configuration
        {
            get
            {
                return _Configuration;
            }
        }

        /// <summary>
        /// Gets the <see cref="CommandMap"/> that this <see cref="Bot"/> uses.
        /// </summary>
        public CommandMap Commands
        {
            get
            {
                return _Commands;
            }
        }

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            if (!_IsDisposed)
            {
                _IsDisposed = true;

                Exit();
                _ExitedEvent.WaitOne();

                _ExitedEvent.Dispose();
                _ExitEvent.Dispose();
            }
        }

        #endregion

        /// <summary>
        /// Runs the message and event processing loop of the bot.
        /// </summary>
        public void Run()
        {
            try
            {
                Configuration.Logger.Log(LogLevel.Information, "Connecting to server");
                _Messenger.Connect();
                try
                {
                    Configuration.Logger.Log(LogLevel.Information, "Connected");
                    Configuration.Logger.Log(LogLevel.Information, "Running message loop");

                    _ExitEvent.WaitOne();
                }
                finally
                {
                    if (_Messenger.Connected)
                    {
                        Configuration.Logger.Log(LogLevel.Information, "Disconnecting from server");
                        _Messenger.Disconnect();
                        Configuration.Logger.Log(LogLevel.Information, "Disconnected");
                    }
                }
            }
            finally
            {
                _ExitedEvent.Set();
            }
        }

        /// <summary>
        /// Ask the message loop running in the <see cref="Run"/> method to exit gracefully.
        /// </summary>
        public void Exit()
        {
            _ExitEvent.Set();
        }

        /// <summary>
        /// Handles the SignedOff event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.SignedOffEventArgs"/> instance containing the event data.</param>
        private void Nameserver_SignedOff(object sender, SignedOffEventArgs e)
        {
        }

        /// <summary>
        /// Handles the SignedIn event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void Nameserver_SignedIn(object sender, EventArgs e)
        {
            _Messenger.ContactList.Owner.Status = PresenceStatus.Online;
        }

        /// <summary>
        /// Handles the ServerErrorReceived event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.MSNErrorEventArgs"/> instance containing the event data.</param>
        private void Nameserver_ServerErrorReceived(object sender, MSNErrorEventArgs e)
        {
            Configuration.Logger.Log(LogLevel.Error, e.MSNError.ToString());
            Exit(); // right response?
        }

        /// <summary>
        /// Handles the SBCreated event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.SBCreatedEventArgs"/> instance containing the event data.</param>
        private void Nameserver_SBCreated(object sender, SBCreatedEventArgs e)
        {
        }

        /// <summary>
        /// Handles the PingAnswer event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.PingAnswerEventArgs"/> instance containing the event data.</param>
        private void Nameserver_PingAnswer(object sender, PingAnswerEventArgs e)
        {
        }

        /// <summary>
        /// Handles the OwnerVerified event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void Nameserver_OwnerVerified(object sender, EventArgs e)
        {
        }

        /// <summary>
        /// Handles the NewMailReceived event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.NewMailEventArgs"/> instance containing the event data.</param>
        private void Nameserver_NewMailReceived(object sender, NewMailEventArgs e)
        {
        }

        /// <summary>
        /// Handles the MobileMessageReceived event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.CrossNetworkMessageEventArgs"/> instance containing the event data.</param>
        private void Nameserver_MobileMessageReceived(object sender, CrossNetworkMessageEventArgs e)
        {
        }

        /// <summary>
        /// Handles the MailboxStatusReceived event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.MailboxStatusEventArgs"/> instance containing the event data.</param>
        private void Nameserver_MailboxStatusReceived(object sender, MailboxStatusEventArgs e)
        {
        }

        /// <summary>
        /// Handles the MailboxChanged event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.MailChangedEventArgs"/> instance containing the event data.</param>
        private void Nameserver_MailboxChanged(object sender, MailChangedEventArgs e)
        {
        }

        /// <summary>
        /// Handles the LeftCircleConversation event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.CircleMemberEventArgs"/> instance containing the event data.</param>
        private void Nameserver_LeftCircleConversation(object sender, CircleMemberEventArgs e)
        {
        }

        /// <summary>
        /// Handles the JoinedCircleConversation event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.CircleMemberEventArgs"/> instance containing the event data.</param>
        private void Nameserver_JoinedCircleConversation(object sender, CircleMemberEventArgs e)
        {
        }

        /// <summary>
        /// Handles the ExceptionOccurred event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.ExceptionEventArgs"/> instance containing the event data.</param>
        private void Nameserver_ExceptionOccurred(object sender, ExceptionEventArgs e)
        {
        }

        /// <summary>
        /// Handles the CrossNetworkMessageReceived event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.CrossNetworkMessageEventArgs"/> instance containing the event data.</param>
        private void Nameserver_CrossNetworkMessageReceived(object sender, CrossNetworkMessageEventArgs e)
        {
        }

        /// <summary>
        /// Handles the ContactStatusChanged event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.ContactStatusChangedEventArgs"/> instance containing the event data.</param>
        private void Nameserver_ContactStatusChanged(object sender, ContactStatusChangedEventArgs e)
        {
        }

        /// <summary>
        /// Handles the ContactOnline event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.ContactEventArgs"/> instance containing the event data.</param>
        private void Nameserver_ContactOnline(object sender, ContactEventArgs e)
        {
        }

        /// <summary>
        /// Handles the ContactOffline event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.ContactEventArgs"/> instance containing the event data.</param>
        private void Nameserver_ContactOffline(object sender, ContactEventArgs e)
        {
        }

        /// <summary>
        /// Handles the CircleTextMessageReceived event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.CircleTextMessageEventArgs"/> instance containing the event data.</param>
        private void Nameserver_CircleTextMessageReceived(object sender, CircleTextMessageEventArgs e)
        {
        }

        /// <summary>
        /// Handles the CircleStatusChanged event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.CircleStatusChangedEventArgs"/> instance containing the event data.</param>
        private void Nameserver_CircleStatusChanged(object sender, CircleStatusChangedEventArgs e)
        {
        }

        /// <summary>
        /// Handles the CircleOnline event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.CircleEventArgs"/> instance containing the event data.</param>
        private void Nameserver_CircleOnline(object sender, CircleEventArgs e)
        {
        }

        /// <summary>
        /// Handles the CircleOffline event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.CircleEventArgs"/> instance containing the event data.</param>
        private void Nameserver_CircleOffline(object sender, CircleEventArgs e)
        {
        }

        /// <summary>
        /// Handles the CircleNudgeReceived event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.CircleMemberEventArgs"/> instance containing the event data.</param>
        private void Nameserver_CircleNudgeReceived(object sender, CircleMemberEventArgs e)
        {
        }

        /// <summary>
        /// Nameserver_s the circle member status changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MSNPSharp.CircleMemberStatusChanged"/> instance containing the event data.</param>
        private void Nameserver_CircleMemberStatusChanged(object sender, CircleMemberStatusChanged e)
        {
        }

        /// <summary>
        /// Handles the CircleMemberOnline event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.CircleMemberEventArgs"/> instance containing the event data.</param>
        private void Nameserver_CircleMemberOnline(object sender, CircleMemberEventArgs e)
        {
        }

        /// <summary>
        /// Handles the CircleMemberOffline event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.CircleMemberEventArgs"/> instance containing the event data.</param>
        private void Nameserver_CircleMemberOffline(object sender, CircleMemberEventArgs e)
        {
        }

        /// <summary>
        /// Handles the AuthenticationError event of the Nameserver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MSNPSharp.ExceptionEventArgs"/> instance containing the event data.</param>
        private void Nameserver_AuthenticationError(object sender, ExceptionEventArgs e)
        {
        }

        /// <summary>
        /// Evemt, ConversationCreated.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MSNPSharp.ConversationCreatedEventArgs"/> instance containing the event data.</param>
        private void ConversationCreated(object sender, ConversationCreatedEventArgs e)
        {
            var session = new Session(this, e.Conversation);
            lock (_Sessions)
                _Sessions.Add(session);
            e.Conversation.SessionClosed += (s2, e2) =>
            {
                lock (_Sessions)
                    _Sessions.Remove(session);
            };
        }
    }
}